
/*
    RateIt
    version 1.0.9
    10/31/2012
    http://rateit.codeplex.com
    Twitter: @gjunge

 */
(function($) {
	$.fn.rateit = function(p1, p2) {
		// quick way out.
		var options = {};
		var mode = 'init';
		var capitaliseFirstLetter = function(string) {
			return string.charAt(0).toUpperCase() + string.substr(1);
		};

		if (this.length == 0)
			return this;

		var tp1 = $.type(p1);
		if (tp1 == 'object' || p1 === undefined || p1 == null) {
			options = $.extend({}, $.fn.rateit.defaults, p1); // wants to init
																// new rateit
																// plugin(s).
		} else if (tp1 == 'string' && p2 === undefined) {
			return this.data('rateit' + capitaliseFirstLetter(p1)); // wants to
																	// get a
																	// value.
		} else if (tp1 == 'string') {
			mode = 'setvalue'
		}

		return this
				.each(function() {
					var item = $(this);

					// shorten all the item.data('rateit-XXX'), will save space
					// in closure compiler, will be like item.data('XXX') will
					// become x('XXX')
					var itemdata = function(key, value) {
						arguments[0] = 'rateit' + capitaliseFirstLetter(key);
						return item.data.apply(item, arguments); // //Fix for
																	// WI: 523
					};

					// add the rate it class.
					if (!item.hasClass('rateit'))
						item.addClass('rateit');

					var ltr = item.css('direction') != 'rtl';

					// set value mode
					if (mode == 'setvalue') {
						if (!itemdata('init'))
							throw 'Can\'t set value before init';

						// if readonly now and it wasn't readonly, remove the
						// eventhandlers.
						if (p1 == 'readonly' && !itemdata('readonly')) {
							item.find('.rateit-range').unbind();
							itemdata('wired', false);
						}
						if (p1 == 'value' && p2 == null)
							p2 = itemdata('min'); // when we receive a null
													// value, reset the score to
													// its min value.

						if (itemdata('backingfld')) {
							// if we have a backing field, check which fields we
							// should update.
							// In case of input[type=range], although we did
							// read its attributes even in browsers that don't
							// support it (using fld.attr())
							// we only update it in browser that support it (&&
							// fld[0].min only works in supporting browsers),
							// not only does it save us from checking if it is
							// range input type, it also is unnecessary.
							var fld = $(itemdata('backingfld'));
							if (p1 == 'value')
								fld.val(p2);
							if (p1 == 'min' && fld[0].min)
								fld[0].min = p2;
							if (p1 == 'max' && fld[0].max)
								fld[0].max = p2;
							if (p1 == 'step' && fld[0].step)
								fld[0].step = p2;
						}

						itemdata(p1, p2);
					}

					// init rateit plugin
					if (!itemdata('init')) {

						// get our values, either from the data-* html5
						// attribute or from the options.
						itemdata('min', itemdata('min') || options.min);
						itemdata('max', itemdata('max') || options.max);
						itemdata('step', itemdata('step') || options.step);
						itemdata(
								'readonly',
								itemdata('readonly') !== undefined ? itemdata('readonly')
										: options.readonly);
						itemdata(
								'resetable',
								itemdata('resetable') !== undefined ? itemdata('resetable')
										: options.resetable);
						itemdata('backingfld', itemdata('backingfld')
								|| options.backingfld);
						itemdata('starwidth', itemdata('starwidth')
								|| options.starwidth);
						itemdata('starheight', itemdata('starheight')
								|| options.starheight);
						itemdata('value', itemdata('value') || options.value
								|| options.min);
						itemdata(
								'ispreset',
								itemdata('ispreset') !== undefined ? itemdata('ispreset')
										: options.ispreset);
						// are we LTR or RTL?

						if (itemdata('backingfld')) {
							// if we have a backing field, hide it, and get its
							// value, and override defaults if range.
							var fld = $(itemdata('backingfld'));
							itemdata('value', fld.hide().val());

							if (fld.attr('disabled') || fld.attr('readonly'))
								itemdata('readonly', true); // http://rateit.codeplex.com/discussions/362055
															// , if a backing
															// field is disabled
															// or readonly at
															// instantiation,
															// make rateit
															// readonly.

							if (fld[0].nodeName == 'INPUT') {
								if (fld[0].type == 'range'
										|| fld[0].type == 'text') { // in
																	// browsers
																	// not
																	// support
																	// the range
																	// type, it
																	// defaults
																	// to text

									itemdata('min', parseInt(fld.attr('min'))
											|| itemdata('min')); // if we
																	// would
																	// have done
																	// fld[0].min
																	// it
																	// wouldn't
																	// have
																	// worked in
																	// browsers
																	// not
																	// supporting
																	// the range
																	// type.
									itemdata('max', parseInt(fld.attr('max'))
											|| itemdata('max'));
									itemdata('step', parseInt(fld.attr('step'))
											|| itemdata('step'));
								}
							}
							if (fld[0].nodeName == 'SELECT'
									&& fld[0].options.length > 1) {
								itemdata('min', Number(fld[0].options[0].value));
								itemdata(
										'max',
										Number(fld[0].options[fld[0].length - 1].value));
								itemdata(
										'step',
										Number(fld[0].options[1].value)
												- Number(fld[0].options[0].value));
							}
						}

						// Create the necessary tags.
						item
								.append('<div class="rateit-reset"></div><div class="rateit-range"><div class="rateit-selected" style="height:'
										+ itemdata('starheight')
										+ 'px"></div><div class="rateit-hover" style="height:'
										+ itemdata('starheight')
										+ 'px"></div></div>');

						// if we are in RTL mode, we have to change the float of
						// the "reset button"
						if (!ltr) {
							item.find('.rateit-reset').css('float', 'right');
							item.find('.rateit-selected').addClass(
									'rateit-selected-rtl');
							item.find('.rateit-hover').addClass(
									'rateit-hover-rtl');
						}

						itemdata('init', true);
					}

					// set the range element to fit all the stars.
					var range = item.find('.rateit-range');
					range.width(
							itemdata('starwidth')
									* (itemdata('max') - itemdata('min')))
							.height(itemdata('starheight'));

					// add/remove the preset class
					var presetclass = 'rateit-preset' + ((ltr) ? '' : '-rtl');
					if (itemdata('ispreset'))
						item.find('.rateit-selected').addClass(presetclass);
					else
						item.find('.rateit-selected').removeClass(presetclass);

					// set the value if we have it.
					if (itemdata('value') != null) {
						var score = (itemdata('value') - itemdata('min'))
								* itemdata('starwidth');
						item.find('.rateit-selected').width(score);
					}

					var resetbtn = item.find('.rateit-reset');
					if (resetbtn.data('wired') !== true) {
						resetbtn.click(function() {
							itemdata('value', itemdata('min'));
							range.find('.rateit-hover').hide().width(0);
							range.find('.rateit-selected').width(0).show();
							if (itemdata('backingfld'))
								$(itemdata('backingfld')).val(itemdata('min'));
							item.trigger('reset');
						}).data('wired', true);

					}

					var calcRawScore = function(element, event) {
						var pageX = (event.changedTouches) ? event.changedTouches[0].pageX
								: event.pageX;

						var offsetx = pageX - $(element).offset().left;
						if (!ltr)
							offsetx = range.width() - offsetx;
						if (offsetx > range.width())
							offsetx = range.width();
						if (offsetx < 0)
							offsetx = 0;

						return score = Math.ceil(offsetx
								/ itemdata('starwidth')
								* (1 / itemdata('step')));
					};

					//

					if (!itemdata('readonly')) {
						// if we are not read only, add all the events

						// if we have a reset button, set the event handler.
						if (!itemdata('resetable'))
							resetbtn.hide();

						// when the mouse goes over the range div, we set the
						// "hover" stars.
						if (!itemdata('wired')) {
							range.bind('touchmove touchend', touchHandler); // bind
																			// touch
																			// events
							range.mousemove(function(e) {
								var score = calcRawScore(this, e);
								var w = score * itemdata('starwidth')
										* itemdata('step');
								var h = range.find('.rateit-hover');
								if (h.data('width') != w) {
									range.find('.rateit-selected').hide();
									h.width(w).show().data('width', w);
									var data = [ (score * itemdata('step'))
											+ itemdata('min') ];
									item.trigger('hover', data).trigger('over',
											data);
								}
							});
							// when the mouse leaves the range, we have to hide
							// the hover stars, and show the current value.
							range.mouseleave(function(e) {
								range.find('.rateit-hover').hide().width(0)
										.data('width', '');
								item.trigger('hover', [ null ]).trigger('over',
										[ null ]);
								range.find('.rateit-selected').show();
							});
							// when we click on the range, we have to set the
							// value, hide the hover.
							range.mouseup(function(e) {
								var score = calcRawScore(this, e);

								var newvalue = (score * itemdata('step'))
										+ itemdata('min');
								itemdata('value', newvalue);
								if (itemdata('backingfld')) {
									$(itemdata('backingfld')).val(newvalue);
								}
								if (itemdata('ispreset')) { // if it was a
															// preset value,
															// unset that.
									range.find('.rateit-selected').removeClass(
											presetclass);
									itemdata('ispreset', false);
								}
								range.find('.rateit-hover').hide();
								range.find('.rateit-selected').width(
										score * itemdata('starwidth')
												* itemdata('step')).show();
								item.trigger('hover', [ null ]).trigger('over',
										[ null ])
										.trigger('rated', [ newvalue ]);
							});

							itemdata('wired', true);
						}
						if (itemdata('resetable')) {
							resetbtn.show();
						}
					} else {
						resetbtn.hide();
					}
				});
	};

	// touch converter
	// http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/
	function touchHandler(event) {

		var touches = event.originalEvent.changedTouches, first = touches[0], type = "";
		switch (event.type) {
		case "touchmove":
			type = "mousemove";
			break;
		case "touchend":
			type = "mouseup";
			break;
		default:
			return;
		}

		var simulatedEvent = document.createEvent("MouseEvent");
		simulatedEvent.initMouseEvent(type, true, true, window, 1,
				first.screenX, first.screenY, first.clientX, first.clientY,
				false, false, false, false, 0/* left */, null);

		first.target.dispatchEvent(simulatedEvent);
		event.preventDefault();
	}
	;

	// some default values.
	$.fn.rateit.defaults = {
		min : 0,
		max : 5,
		step : 0.5,
		starwidth : 16,
		starheight : 16,
		readonly : false,
		resetable : true,
		ispreset : false
	};

	// invoke it on all div.rateit elements. This could be removed if not
	// wanted.
	$(function() {
		$('div.rateit').rateit();
	});

})(jQuery);
